#include <iostream>
#include <string>
#include <vector>
#include <unistd.h> // fork
#include <cstdlib>  // exit
#include <cassert>  // assert
#include <ctime>    // time
#include <sys/types.h> // waitpid
#include <sys/wait.h>  // waitpid
using namespace std;

#define Process_Num 5
#define Make_Seed() srand((unsigned long)time(nullptr) ^ getpid() ^ 0x171237 ^ rand() % 1234);
// -----------子进程要完成的某种任务---------------
typedef void(*func_t)(); // 函数指针

void downLoadTask() { cout << getpid() << ": 下载任务\n" << endl;  sleep(1); }
void ioTask() { cout << getpid() << ": IO任务\n" << endl;  sleep(1); }
void flushTask() { cout << getpid() << ": 刷新任务\n" << endl;  sleep(1); }

void loadTaskFunc(vector<func_t>& funcMap) {
    funcMap.push_back(downLoadTask);
    funcMap.push_back(ioTask);
    funcMap.push_back(flushTask);
}

// -----------下面的代码是多进程程序---------------
class SubEndPoint { // 用于表示子进程i及其对应的管道
public:
    SubEndPoint(pid_t subId, int writeFd)
    :_subId(subId), _writeFd(writeFd) {
        char nameBuffer[1024];
        snprintf(nameBuffer, sizeof nameBuffer, "process-%d[pid(%d)-fd(%d)]", _num++, _subId, _writeFd);
        _name = nameBuffer;
    }
    static int _num;
    string _name;
    pid_t _subId; // 子进程PID
    int _writeFd; // 父进程对于(子进程对应管道)写端的文件描述符
};
int SubEndPoint::_num = 0;

int receiveTask(int readFd) { // 从管道的读端接收命令码
    int code = 0;
    ssize_t s = read(readFd, &code, sizeof(code));
    if(s == 4) { return code; }
    else if(s <= 0) { return -1; } // 管道中的数据读完了，可以让子进程结束运行了
}

void sendTask(const SubEndPoint& process, int taskIndex) { // 把任务发送给选择的进程
    cout << "send task" << taskIndex << " to " << process._name << endl;
    int n = write(process._writeFd, &taskIndex, sizeof(taskIndex));
    assert(n == (sizeof(int))); // 保证发送成功
}

// 创建子进程i及其对应的管道
void createSubProcess(vector<SubEndPoint>& subs, vector<func_t>& funcMap) {
    vector<int> deleteFd;
    for(int i = 0; i < Process_Num; ++i) {
        int fds[2];
        int n = pipe(fds);  assert(n == 0);
        pid_t id = fork();
        if(id == 0) { // 子进程
            for(int i = 0; i < deleteFd.size(); ++i) { close(deleteFd[i]); } // 关闭子进程i继承的对子进程1~i-1的写端
            close(fds[1]); // 关闭子进程对管道的写端
            while(true) {
                // 1. 获取命令码，若父进程没有发送，则该子进程应阻塞
                int commandCode = receiveTask(fds[0]);
                // 2. 完成任务
                if(commandCode >= 0 && commandCode < funcMap.size()) {
                    funcMap[commandCode]();
                } 
                else if(commandCode == -1) { break; } // 结束子进程的运行
            }
            exit(0);
        }
        close(fds[0]); // 父进程关闭读端
        SubEndPoint sub(id, fds[1]); // 保存当前子进程的<id, 对应管道写端的fd>
        subs.push_back(sub);
        deleteFd.push_back(fds[1]);
    }
}
// 负载均衡地控制
void loadBlance_Control(const vector<SubEndPoint>& subs, const vector<func_t>& funcMap, int taskCount) {
    int processNum = subs.size();
    int taskNum = funcMap.size();
    bool forever = (taskCount == 0) ? true : false;
    while(taskCount || forever) {
        int subIndex = rand() % processNum;  // [1] 选择1个子进程(随机数法) → 负载均衡
        int taskIndex = rand() % taskNum;    // [2] 选择1个任务
        sendTask(subs[subIndex], taskIndex); // [3] 把任务发送给选择的进程
        taskCount--;  sleep(2);
    }
    for(int i = 0; i < processNum; ++i) { close(subs[i]._writeFd); } // 关闭父进程对所有管道的写端
    // 写端关闭后，子进程从对应管道的读端会一直读取到0，即将管道中的数据读完了，此时可以该子进程可以退出了
}

void waitProcess(const vector<SubEndPoint>& subs) { // 回收子进程信息
    int processNum = subs.size();
    for(int i = 0; i < processNum; ++i) {
        waitpid(subs[i]._subId, nullptr, 0); // 阻塞式等待，不拿退出码
        cout << "wait subProcess " << subs[i]._subId << " success" << endl;
    }
}

int main() {
    Make_Seed(); // 生成随机数种子
    // 1. 建立子进程，并建立和子进程通信的管道，<子进程id, writeFd>
    vector<func_t> funcMap;
    loadTaskFunc(funcMap);  // 加载方法表
    vector<SubEndPoint> subs;
    createSubProcess(subs, funcMap); // 创建子进程并维护好父子通信信道 
    // 2. (只有父进程能走到这里)父进程控制子进程，负载均衡地向子进程发送命令码
    int taskCount = 3; // 共20个任务，为0则永远进行
    loadBlance_Control(subs, funcMap, taskCount);
    // 3. 回收子进程信息
    waitProcess(subs);
    return 0;
}